package net.stogi.unicore.beyond.back.service.impl;

import java.util.ArrayList;
import java.util.List;

import net.stogi.common.api.exception.BusinessException;
import net.stogi.common.api.exception.Messages;
import net.stogi.unicore.beyond.api.dto.TaskInstance;
import net.stogi.unicore.beyond.api.exception.ValidationException;
import net.stogi.unicore.beyond.api.model.InputDefinition;
import net.stogi.unicore.beyond.api.model.InputType;
import net.stogi.unicore.beyond.api.model.OutputType;
import net.stogi.unicore.beyond.api.model.TaskDefinition;
import net.stogi.unicore.beyond.api.service.TaskService;
import net.stogi.unicore.beyond.back.dao.InputTypeDao;
import net.stogi.unicore.beyond.back.dao.OutputTypeDao;
import net.stogi.unicore.beyond.back.dao.TaskDefinitionDao;
import net.stogi.unicore.beyond.back.model.util.ModelUtils;
import net.stogi.unicore.beyond.io.input.core.InputValidator;
import net.stogi.unicore.beyond.io.input.core.InputValidatorRegistry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("UnicoreBeyondBack.Service.TaskService")
public class TaskServiceImpl implements TaskService {

	private static final Logger log = LoggerFactory.getLogger(TaskServiceImpl.class);

	@Autowired
	private InputTypeDao inputTypeDao;

	@Autowired
	private InputValidatorRegistry inputValidatorRegistry;

	@Autowired
	private OutputTypeDao outputTypeDao;

	@Autowired
	private TaskDefinitionDao taskDefinitionDao;

	@Override
	@Transactional(readOnly = true, rollbackFor = BusinessException.class)
	public TaskDefinition getTaskDefinition(Long id) throws BusinessException {
		TaskDefinition taskDefinition = taskDefinitionDao.loadById(id, false);
		return ModelUtils.copyTaskDefinition(taskDefinition, true);
	}

	@Override
	@Transactional(readOnly = true, rollbackFor = BusinessException.class)
	public List<InputType> listInputTypes() {
		List<InputType> inputTypes = new ArrayList<InputType>();

		for (InputType inputType : inputTypeDao.loadAll()) {
			inputTypes.add(ModelUtils.copyInputType(inputType));
		}

		log.debug("Listed {} input types", inputTypes.size());

		return inputTypes;
	}

	@Override
	@Transactional(readOnly = true, rollbackFor = BusinessException.class)
	public List<OutputType> listOutputTypes() {
		List<OutputType> outputTypes = new ArrayList<OutputType>();

		for (OutputType outputType : outputTypeDao.loadAll()) {
			outputTypes.add(ModelUtils.copyOutputType(outputType));
		}

		log.debug("Listed {} output types", outputTypes.size());

		return outputTypes;
	}

	@Override
	@Transactional(readOnly = true, rollbackFor = BusinessException.class)
	public List<TaskDefinition> listTaskDefinitions() throws BusinessException {
		List<TaskDefinition> taskDefinitions = new ArrayList<TaskDefinition>();

		for (TaskDefinition taskDefinition : taskDefinitionDao.loadAll()) {
			log.trace("Loaded taskDefinition: [ taskDefinition = '{}' ]", taskDefinition.toString());

			taskDefinitions.add(ModelUtils.copyTaskDefinition(taskDefinition, true));
		}

		log.debug("Listed {} task definitions", taskDefinitions.size());

		for (TaskDefinition taskDefinition : taskDefinitions) {
			log.trace("Listed taskDefinition: [ taskDefinition = '{}' ]", taskDefinition.toString());
		}

		return taskDefinitions;
	}

	@Override
	@Transactional(rollbackFor = BusinessException.class)
	public void runTaskInstance(TaskInstance taskInstance) throws ValidationException {
		log.trace("runTaskInstance(): Invoke [ taskInstance = '{}' ]", taskInstance);

		Messages messages = new Messages();

		// Validate task instance against task definition.
		for (InputDefinition inputDefinition : taskInstance.getTaskDefinition().selectAllInputDefinitions()) {
			InputType inputType = inputDefinition.getInputType();

			// Load validator.
			InputValidator validator = inputValidatorRegistry.findInputValidator(inputType);
			if (validator == null) {
				log.error("runTaskInstance(): There is no validator for input type [ inputType.name = '{}' ]", inputType.getName());
				throw new IllegalStateException("There is no validator for input type [ inputType.name = '" + inputType.getName() + "' ]");
			}

			// Validate.
			String inputName = inputDefinition.getName();
			validator.validate(inputName, taskInstance.getInput(inputName), inputDefinition.getProperties(), messages);
		}

		// Errors occurs.
		if (messages.hasErrors()) {
			log.warn("runTaskInstance(): There was errors while validating task instance");
			throw new ValidationException(messages);
		}

		// TODO: Run.
	}

	@Override
	@Transactional(rollbackFor = BusinessException.class)
	public void saveTaskDefinition(TaskDefinition taskDefinition) throws BusinessException {
		log.trace("saveTaskDefinition(): Incoming task definition: \n{}", taskDefinition.toString());

		TaskDefinition persitedTaskDefinition = null;

		Long id = taskDefinition.getId();
		if (id != null) {
			persitedTaskDefinition = taskDefinitionDao.loadById(id, true);

			if (persitedTaskDefinition == null) {
				log.error("saveTaskDefinition(): There is no TaskDefinition with given id: {}", id);
				throw new BusinessException("There is no TaskDefinition with given id: " + id);
			}

			log.trace("saveTaskDefinition(): Persisted task definition to update: \n{}", persitedTaskDefinition.toString());
		}

		if (persitedTaskDefinition == null) {
			persitedTaskDefinition = taskDefinition;
		}
		else {
			ModelUtils.updateTaskDefinition(taskDefinition, persitedTaskDefinition);

			log.trace("saveTaskDefinition(): Persisted task definition after update: \n{}", persitedTaskDefinition.toString());
		}

		taskDefinitionDao.save(persitedTaskDefinition);

		log.trace("saveTaskDefinition(): Persisted task definition after save: \n{}", persitedTaskDefinition.toString());
		log.debug("saveTaskDefinition(): Saved task definition [ id = '{}' ]", persitedTaskDefinition.getId());
	}
}
